#include "win_raw_sound_ostream.h"
#include <iostream>
#include <string>
#include <cstring>
#include "win_exception.h"
#include <process.h>

using namespace hard;


win_raw_sound_ostream::chunk::~chunk()
{
  MMRESULT ret=666;
  try {
    if(unprepare_)
    {
      while((ret = waveOutUnprepareHeader(hWaveOut, header, sizeof(WAVEHDR))) == WAVERR_STILLPLAYING)
        Sleep(100);
      X_WIN_ERROR_THROW_IF(ret != MMSYSERR_NOERROR, ret, "~chunk: waveOutUnprepareHeader ");
      delete[] header->lpData;
      delete header;
      header = 0;
    }
  }
  catch(win_exception& e)
  {
    std::cout<<e.what()<<"\nerror number: "<<e.error_number()<<std::endl;
    throw e;
  }
}

void win_raw_sound_ostream::chunk::init(HWAVEOUT hWaveOut, LPSTR block, DWORD size)
{
  /*
    * initialise the block header with the size
    * and pointer.
    */
  header = new WAVEHDR;
  ZeroMemory(header, sizeof(WAVEHDR));
  header->dwBufferLength = size;
  header->lpData = new char[size];
  memcpy(header->lpData,block,size);
  /*
    * prepare the block for playback
    */
  waveOutPrepareHeader(hWaveOut, header, sizeof(WAVEHDR));
}


unsigned int WINAPI hard::win_raw_sound_ostream_thread(void* arg)
{
  try {
    win_raw_sound_ostream* stream = (win_raw_sound_ostream*) arg;

    HANDLE lpHandles[2] = {stream->endThread, stream->writeEvent};
    DWORD ret;
    while(true)
    {
      ret = WaitForMultipleObjects(2, lpHandles, FALSE, INFINITE);
      stream->chr.start();
      X_WIN_THROW_IF(ret==WAIT_FAILED, "win_raw_sound_ostream_thread: WaitForMultipleObjects");
      if(ret-WAIT_OBJECT_0 == 0 || stream->end || 0==WaitForSingleObject(stream->endThread,0))
        break;
      else if(ret-WAIT_OBJECT_0 == 1)
      {
       while(!(stream->end || 0==WaitForSingleObject(stream->endThread,0)) 
         && stream->write_next() != stream->QUEUE_EMPTY);
      }
      else
       throw std::runtime_error("win_raw_sound_ostream_thread: WaitForMultipleObjects unkwon event");

      stream->chr.stop();
    }
    return 0;
  }
  catch(win_exception& e)
  {
    std::cout<<e.what()<<"\nerror number: "<<e.error_number()<<std::endl;
    return e.error_number();
  }
  catch(std::exception& e)
  {
    std::cout<<e.what()<<std::endl;
    return -1;
  }
}


win_raw_sound_ostream::win_raw_sound_ostream(DWORD nSamplesPerSec, WORD wBitsPerSample, WORD nChannels): end(false), chr(0)
{
  /*
   * first we need to set up the WAVEFORMATEX structure. 
   * the structure describes the format of the audio.
   */
  wfx.nSamplesPerSec = nSamplesPerSec; /* sample rate */
  wfx.wBitsPerSample = wBitsPerSample; /* sample size */
  wfx.nChannels = nChannels; /* channels*/
  /*
   * WAVEFORMATEX also has other fields which need filling.
   * as long as the three fields above are filled this should
   * work for any PCM (pulse code modulation) format.
   */
  wfx.cbSize = 0; /* size of _extra_ info */
  wfx.wFormatTag = WAVE_FORMAT_PCM;
  wfx.nBlockAlign = (wfx.wBitsPerSample >> 3) * wfx.nChannels;
  wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;

  InitializeCriticalSection(&csWriting);

  endThread = CreateEvent(NULL, TRUE, FALSE, NULL);//"Local\floaterEndThread");
  X_WIN_THROW_IF_NULL(endThread, "win_raw_sound_ostream: CreateEvent");

  writeEvent = CreateEvent(NULL, FALSE, FALSE, NULL);//"Local\floaterwriteEvent");
  X_WIN_THROW_IF_NULL(writeEvent, "win_raw_sound_ostream: CreateEvent");

  openResult = waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL/*|WAVE_ALLOWSYNC|WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE*//*|WAVE_FORMAT_DIRECT*/);

  thread = (HANDLE) _beginthreadex(NULL, 0, win_raw_sound_ostream_thread, this, 0, &threadId);
  X_WIN_THROW_IF_NULL(thread, "win_raw_sound_ostream: CreateThread");

  X_WIN_THROW_IF_NULL(SetThreadPriority(thread,THREAD_PRIORITY_TIME_CRITICAL), "tracker: SetThreadPriority");
}

void win_raw_sound_ostream::close()
{
  end = true;

  X_WIN_THROW_IF_NULL(SetEvent(endThread), "~win_raw_sound_ostream: SetEvent(endThread)");

  DWORD ret=STILL_ACTIVE;
  while(true)
  {
    X_WIN_THROW_IF_NULL(GetExitCodeThread(thread,&ret), "~win_raw_sound_ostream: WaitForSingleObject");
    if(ret!=STILL_ACTIVE)
      break;
    Sleep(100);
  }

  X_WIN_THROW_IF_NULL(CloseHandle(thread), "~win_raw_sound_ostream: CloseHandle(thread)");
  X_WIN_THROW_IF_NULL(CloseHandle(endThread), "~win_raw_sound_ostream: CloseHandle(endThread)");
  X_WIN_THROW_IF_NULL(CloseHandle(writeEvent), "~win_raw_sound_ostream: CloseHandle(writeEvent)");
  clean_written();
  waveOutClose(hWaveOut);
  DeleteCriticalSection(&csWriting);
}

win_raw_sound_ostream& win_raw_sound_ostream::write(const void* s , std::streamsize n)
{
  X_THROW_RUNTIME_IF(!is_open(), "write: couldn't open audio device");
  EnterCriticalSection(&csWriting);
  chunks.push(chunk(hWaveOut, (LPSTR)s, n));
  X_WIN_THROW_IF_NULL(SetEvent(writeEvent), "write: SetEvent(writeEvent)");
  LeaveCriticalSection(&csWriting);
  return *this;
}

MMRESULT win_raw_sound_ostream::write_next()
{
  X_THROW_RUNTIME_IF(!is_open(), "write_next: couldn't open audio device");
  EnterCriticalSection(&csWriting);
  if(!chunks.empty())
  {
    result = waveOutWrite(hWaveOut, chunks.front().header, sizeof(WAVEHDR));
    written.push(chunks.front().unprepare());
    chunks.pop();
  }
  else
    result = QUEUE_EMPTY;
  LeaveCriticalSection(&csWriting);
  return result;
}

void win_raw_sound_ostream::clean_written()
{
  while(!written.empty())
    written.pop();
}